package com.dc.security;

import java.io.File;
import java.io.FileInputStream;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyStore;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Provider;
import java.security.Security;
import java.security.Signature;
import java.security.SignatureException;
import java.security.cert.Certificate;
import java.util.Set;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class JACUtil {

	private static JACUtil instance = null;
	
	static Log logger = LogFactory.getLog(JACUtil.class);
	
	public static JACUtil getInstance(String providerName)
	{
		if (instance == null)
		{
			if (providerName != null)
			{
				Provider provider;
				try {
					provider = (Provider) Class.forName(providerName).newInstance();
					
					Security.insertProviderAt(provider, 1);
				} catch (InstantiationException e) {
					logger.error(e);
				} catch (IllegalAccessException e) {
					logger.error(e);
				} catch (ClassNotFoundException e) {
					logger.error(e);
				}
				
			}
			instance = new JACUtil();
		}
		
		return instance;
	}
	
	private JACUtil()
	{
		
	}
	
	/**
	 * 判断给定的算法在当前jvm内提供的安全算法中是否存�?
	 * @param algorithm 算法名称
	 * @return true=存在此算法，false=不存�?
	 */
	public boolean isSupportedAlgorithm(String algorithm)
	{
		boolean result = false;
		
		if (algorithm == null || "".equals(algorithm))
			return result;
		algorithm = algorithm.split("/")[0];
		
		for (Provider provider : Security.getProviders())
		{
			for (Provider.Service service : provider.getServices())
			{
				if (algorithm.equals(service.getAlgorithm()))
				{
					result = true;
					break;
				}
			}
			if (result)
				break;
		}
		
		return result;
	}
	
	/**
	 * 判断给定的算法在当前jvm内提供的安全算法中是否存�?
	 * @param algorithm 算法名称
	 * @param type 算法类型：Signature、MessageDigest、Cipher、Mac、KeyStore
	 * @return true=存在此算法，false=不存�?
	 */
	public boolean isSupportedAlgorithm(String algorithm, String type)
	{
		boolean result = false;
		
		if (algorithm == null || "".equals(algorithm))
			return result;
		algorithm = algorithm.split("/")[0];
		
		Set<String> algorithms = Security.getAlgorithms(type);
		
		for (String item : algorithms)
		{
			if (algorithm.equals(item))
			{
				result = true;
				break;
			}
		}
		
		return result;
	}

	/**
	 * 产生给定算法及其长度的对称加密密�?
	 * @param algorithm
	 * @param size
	 * @return
	 */
	public Key generateSymmeticKey(String algorithm, int size)
	{
		Key result = null;
		
		if (algorithm == null || "".equals(algorithm))
			return result;
		algorithm = algorithm.split("/")[0];
		
		try {
			KeyGenerator generator = KeyGenerator.getInstance(algorithm);
			generator.init(size);
			
			result = generator.generateKey();

		} catch (NoSuchAlgorithmException e) {
			logger.error("密钥产生错误，不支持的加密算�?! algorithm:" + algorithm);
		}
		
		return result;
	}
	
	/**
	 * 根据给定的加解密算法和密钥，将明文加密，返回密文。此函数认为传入的算法和key都是provider支持的�??
	 * @param plain 明文
	 * @param algorithm 加密算法：可是单独的算法名称(其他使用默认�?)，也可是 算法/模块/填充�?
	 * @param key
	 * @return
	 */
	public byte[] encrypt(byte[] plain, String algorithm, Key key)
	{
		byte[] result = null;
		
		if (algorithm == null || "".equals(algorithm))
			return result;
		algorithm = algorithm.split("/")[0];
		
		try {
			Cipher cipher = Cipher.getInstance(algorithm);
			cipher.init(Cipher.ENCRYPT_MODE, key);
			
			result = cipher.doFinal(plain);
			
		} catch (NoSuchAlgorithmException e) {
			logger.error("加密错误，不支持的加密算�?! algorithm:" + algorithm);
		} catch (NoSuchPaddingException e) {
			logger.error("加密错误，不支持的加密算法填充方�?! algorithm:" + algorithm);
		} catch (InvalidKeyException e) {
			logger.error("加密错误，密钥无�?! key:" + key.toString());
			logger.error(e.getMessage());
		} catch (IllegalBlockSizeException e) {
			logger.error(e);
		} catch (BadPaddingException e) {
			logger.error(e);
		}
		
		return result;
	}
	
	/**
	 * 根据给定的加解密算法和密钥，将密文解密，返回密文。此函数认为传入的算法和key都是provider支持的�??
	 * @param cipherText 密文
	 * @param algorithm 解密算法：可是单独的算法名称(其他使用默认�?)，也可是 算法/模块/填充�?
	 * @param key 密钥
	 * @return
	 */
	public byte[] decrypt(byte[] cipherData, String algorithm, Key key)
	{
		byte[] result = null;
		
		if (algorithm == null || "".equals(algorithm))
			return result;
		algorithm = algorithm.split("/")[0];
		
		try {
			Cipher cipher = Cipher.getInstance(algorithm);
			cipher.init(Cipher.DECRYPT_MODE, key);
			
			result = cipher.doFinal(cipherData);
			
		} catch (NoSuchAlgorithmException e) {
			logger.error("解密错误，不支持的加密算�?! algorithm:" + algorithm);
		} catch (NoSuchPaddingException e) {
			logger.error("解密错误，不支持的加密算法填充方�?! algorithm:" + algorithm);
		} catch (InvalidKeyException e) {
			logger.error("解密错误，密钥无�?! key:" + key.toString());
			logger.error(e.getMessage());
		} catch (IllegalBlockSizeException e) {
			logger.error(e);
		} catch (BadPaddingException e) {
			logger.error(e);
		}
		
		return result;
	}
	
	/**
	 * 将给定的数据，按照给定的算法摘要
	 * @param plain
	 * @param algorithm
	 * @return
	 */
	public byte[] digest(byte[] plain, String algorithm)
	{
		byte[] result = null;
		
		if (algorithm == null || "".equals(algorithm))
			return result;
		algorithm = algorithm.split("/")[0];
		
		try {
			MessageDigest messageDigest = MessageDigest.getInstance(algorithm);
			result = messageDigest.digest(plain);
			
		} catch (NoSuchAlgorithmException e) {
			logger.error("摘要错误，不支持的摘要算�?! algorithm:" + algorithm);
		}
		
		return result;
	}
	
	/**
	 * 将给定的数据，按照给定的算法摘要，并返回base64编码后的字符�?
	 * @param plain
	 * @param algorithm
	 * @return
	 */
	public String base64Digest(byte[] plain, String algorithm)
	{
		String result = null;
		
		byte[] data = digest (plain, algorithm);
		
		if (data != null)
			result = new sun.misc.BASE64Encoder().encode(data);
		
		return result;
	}
	
	/**
	 * 进行数据签名
	 * @param plain		要签名的数据
	 * @param algorithm	签名算法
	 * @param key	签名私钥
	 * @return	签名后数�?
	 */
	public byte[] sign(byte[] plain, String algorithm, PrivateKey key)
	{
		byte[] result = null;
		
		if (algorithm == null)
			return result;
		
		try {
			Signature signature = Signature.getInstance(algorithm);
			signature.initSign(key);
			
			signature.update(plain);
			result = signature.sign();
			
		} catch (NoSuchAlgorithmException e) {
			logger.error("签名错误，不支持的签名算�?! algorithm:" + algorithm);
		} catch (InvalidKeyException e) {
			logger.error("签名错误，密钥无�?! key:" + key.toString());
			logger.error(e.getMessage());
		} catch (SignatureException e) {
			logger.error(e);
		}
		
		return result;
	}
	
	/**
	 * 验证签名数据
	 * @param plain	要签名的数据
	 * @param signatureData 签名后数�?
	 * @param algorithm 签名算法
	 * @param cert 签名证书
	 * @return 是否是合法签�?
	 */
	public boolean verifySign(byte[] plain, byte[] signatureData, String algorithm, Certificate cert)
	{
		boolean result = false;
		
		if (algorithm == null)
			return result;

		try {
			Signature signature = Signature.getInstance(algorithm);
			signature.initVerify(cert);
			
			signature.update(plain);
			result = signature.verify(signatureData);
			
		} catch (NoSuchAlgorithmException e) {
			logger.error("签名验证错误，不支持的签名算�?! algorithm:" + algorithm);
		} catch (InvalidKeyException e) {
			logger.error("签名验证错误，密钥无�?! cert:" + cert.toString());
			logger.error(e.getMessage());
		} catch (SignatureException e) {
			logger.error(e);
		}
		
		return result;
	}
	
	
	
	public static void main(String[] args) throws Exception, IllegalAccessException, ClassNotFoundException
	{
		JACUtil manager = JACUtil.getInstance(null);
		
		String text = "WangJian";
		
		String userHome = System.getProperty("user.home");
		String keystoreFilename = userHome + File.separator + ".keystore";
		
		FileInputStream in = new FileInputStream(keystoreFilename);
		KeyStore keyStore = KeyStore.getInstance("JKS");
		
		keyStore.load(in, "password".toCharArray());
		
		Certificate cert = keyStore.getCertificate("DCESB");
		
		PrivateKey privateKey = (PrivateKey) keyStore.getKey("DCESB", "password".toCharArray());
		
		byte[] signData = manager.sign(text.getBytes(), "MD5withRSA", privateKey);
//		signData[10] = 1;
		boolean b = manager.verifySign(text.getBytes(), signData, "MD5withRSA", cert);
		
		
		
		
	}

}
